package reliability;

import reliability.model.GraphModel;
import reliability.types.graph.*;
import reliability.types.logical_function.LogicalAndOperation;
import reliability.types.logical_function.LogicalFunctionElement;
import reliability.types.logical_function.LogicalOrOperation;
import reliability.types.logical_function.LogicalVariable;
import reliability.types.logical_function.ParenExpression;

import java.util.Iterator;
import java.util.Map;
import java.util.Stack;
import java.util.Vector;

public class GraphParser
{
    private Edges edges;
    private Nodes nodes;

    private class VectorOfLogicalFunctionElements
            extends Vector<LogicalFunctionElement> {}

    public GraphParser(GraphModel graph)
    {
        this.edges = graph.getEdges();
        this.nodes = graph.getNodes();
    }

    public LogicalFunctionElement parseLogicalStructureFunction(VectorOfMinimalPaths vectorOfMinimalPaths)
    {
        VectorOfLogicalFunctionElements structureFunctionOperands = new VectorOfLogicalFunctionElements();
        for (PathsInfo pathsInfo : vectorOfMinimalPaths)
        {
            VectorOfLogicalFunctionElements argumentsForDisjunction = new VectorOfLogicalFunctionElements();
            for (Path path : pathsInfo.paths)
            {
                VectorOfLogicalFunctionElements logicalFunctionElements = getLogicalVariablesFromPath(path);
                argumentsForDisjunction.add(conjunction(logicalFunctionElements));
            }
            structureFunctionOperands.add(new ParenExpression(disjunction(argumentsForDisjunction)));
        }
        return conjunction(structureFunctionOperands);
    }

    public VectorOfMinimalPaths getMinimalPaths()
    {
        Integer [] nodeKeys = nodes.keySet().toArray(new Integer[nodes.size()]);
        VectorOfMinimalPaths vectorOfMinimalPaths = new VectorOfMinimalPaths();
        for (int i = 0; i < nodes.size(); i++)
        {
            for (int j = i + 1; j < nodes.size(); j++)
            {
                Paths paths = parsePathsBetweenNodes(nodeKeys[i], nodeKeys[j]);
                if (!paths.isEmpty())
                {
                    vectorOfMinimalPaths.add(new PathsInfo(paths, nodeKeys[i], nodeKeys[j]));
                }
                else
                {
                    return null;//TODO: поправить эту херню
                }
            }
        }
        return vectorOfMinimalPaths;
    }

    private Paths parsePathsBetweenNodes(Integer startNodeKey, Integer finishNodeKey)
    {
        Paths paths = new Paths();
        Stack<Iterator<Map.Entry<Integer, Edge>>> iteratorStack = new Stack<Iterator<Map.Entry<Integer, Edge>>>();
        iteratorStack.push(edges.entrySet().iterator());
        Path path = new Path();
        path.push(startNodeKey);
        while (!path.empty())
        {
            Integer currentNodeKey = path.peek();
            Iterator<Map.Entry<Integer, Edge>> currentIterator = iteratorStack.peek();

            Integer edgeKey = null;
            while (currentIterator.hasNext() && edgeKey == null)
            {
                Map.Entry<Integer, Edge> entry = currentIterator.next();
                Integer key = entry.getKey();
                Edge edge = entry.getValue();
                if ((currentNodeKey.equals(edge.getNode1Key()) && !path.contains(edge.getNode2Key())) ||
                        (currentNodeKey.equals(edge.getNode2Key())) && !path.contains(edge.getNode1Key())
                        )
                {
                    edgeKey = key;
                    iteratorStack.push(edges.entrySet().iterator());
                }
            }
            if (edgeKey != null)
            {
                Edge edge = edges.get(edgeKey);
                Integer neighbourNodeKey = edge.getNeighbourKey(currentNodeKey);
                path.push(edgeKey);
                path.push(neighbourNodeKey);
                iteratorStack.push(edges.entrySet().iterator());
                if (neighbourNodeKey.equals(finishNodeKey))
                {
                    path.pop();
                    iteratorStack.pop();
                    paths.add(trimPath(path));
                }
            }
            else
            {
                path.pop();
                iteratorStack.pop();
            }
        }
        return paths;
    }

    private Path trimPath(Path path)
    {
        Path minimalPath = (Path) path.clone();
        minimalPath.remove(0);
        return minimalPath;
    }

    private VectorOfLogicalFunctionElements getLogicalVariablesFromPath(Path path)
    {
        VectorOfLogicalFunctionElements logicalVariables = new VectorOfLogicalFunctionElements();
        boolean isEdge = true;
        for (Integer curElKey : path)
        {
            double reliability = (isEdge) ? edges.get(curElKey).getReliability() : nodes.get(curElKey).getReliability();
            isEdge = !isEdge;
            logicalVariables.add(new LogicalVariable(reliability, curElKey));
        }
        return logicalVariables;
    }

    LogicalFunctionElement conjunction(VectorOfLogicalFunctionElements vectorOfLogicalFunctionElements)
    {
        int vectorSize = vectorOfLogicalFunctionElements.size();
        if (vectorSize == 1)
        {
            return vectorOfLogicalFunctionElements.get(0);
        }
        LogicalFunctionElement logicalAndOperation = vectorOfLogicalFunctionElements.get(0);
        for (int i = 1; i < vectorOfLogicalFunctionElements.size(); i++)
        {
            LogicalFunctionElement nextOperand = vectorOfLogicalFunctionElements.get(i);
            logicalAndOperation = new LogicalAndOperation(logicalAndOperation, nextOperand);
        }
        return logicalAndOperation;
    }

    LogicalFunctionElement disjunction(VectorOfLogicalFunctionElements vectorOfLogicalFunctionElements)
    {
        int vectorSize = vectorOfLogicalFunctionElements.size();
        if (vectorSize == 1)
        {
            return vectorOfLogicalFunctionElements.get(0);
        }
        LogicalFunctionElement logicalOrOperation = vectorOfLogicalFunctionElements.get(0);
        for (int i = 1; i < vectorOfLogicalFunctionElements.size(); i++)
        {
            LogicalFunctionElement nextOperand = vectorOfLogicalFunctionElements.get(i);
            logicalOrOperation = new LogicalOrOperation(logicalOrOperation, nextOperand);
        }
        return logicalOrOperation;
    }

}

